import numpy as np


def mutation(self):
    '''
    mutation of 0/1 type chromosome
    faster than `self.Chrom = (mask + self.Chrom) % 2`
    :param self:
    :return:
    '''
    #
    bit_precision = int(2 - np.log2(self.prob_mut))

    mask_tmp0 = np.random.randint(0, np.iinfo(self.number_type).max + 1, (self.size_pop, self.len_chrom, bit_precision),
                                  dtype=self.number_type)
    bit_prob = np.array(self.prob_mut * (1 << bit_precision), dtype=self.number_type)
    mask = np.zeros((self.size_pop, self.len_chrom), dtype=self.number_type)
    mask_tmp1 = np.ones((self.size_pop, self.len_chrom), dtype=self.number_type) * (1 << self.Lind) - 1
    for i in range(bit_precision - 1, 0, -1):
        # random high bit equal to 1 and constant low bit equal to 0 set mask_tmp1 to 0
        # random high bit equal to 0 and constant low bit equal to 1 set mask to 1
        mutate_bit = ~mask_tmp0[:, :, i]
        high_bit = bit_prob >> np.uint(i)
        constant_bit = (~high_bit & self.number_type(1)) - self.number_type(1)

        mask_tmp1 &= mutate_bit | constant_bit
        mask |= mask_tmp1 & mutate_bit & constant_bit
    self.Chrom ^= mask
    # [np.sum((mask)>>i&1==0)/432/4096 for i in range(64)]
    return self.Chrom


def swap(individual):
    n1, n2 = np.random.randint(0, individual.shape[0] - 1, 2)
    if n1 >= n2:
        n1, n2 = n2, n1 + 1
    individual[n1], individual[n2] = individual[n2], individual[n1]
    return individual


def reverse(individual):
    '''
    Reverse n1 to n2
    Also called `2-Opt`: removes two random edges, reconnecting them so they cross
    Karan Bhatia, "Genetic Algorithms and the Traveling Salesman Problem", 1994
    https://pdfs.semanticscholar.org/c5dd/3d8e97202f07f2e337a791c3bf81cd0bbb13.pdf
    '''
    n1, n2 = np.random.randint(individual.shape[0], size=2)
    if n1 >= n2:
        n1, n2 = n2, n1 + 1
    individual[n1:n2] = individual[n1:n2][::-1]
    return individual


def transpose(individual):
    # randomly generate n1 < n2 < n3. Notice: not equal
    n1, n2, n3 = sorted(np.random.randint(individual.shape[0] - 2, size=3))
    n2 += 1
    n3 += 2
    slice1, slice2, slice3, slice4 = individual[0:n1], individual[n1:n2], individual[n2:n3 + 1], individual[n3 + 1:]
    individual = np.concatenate([slice1, slice3, slice2, slice4])
    return individual


def mutation_reverse(self):
    '''
    Reverse
    :param self:
    :return:
    '''
    for i in range(self.size_pop):
        if np.random.rand() < self.prob_mut:
            self.Chrom[i] = reverse(self.Chrom[i])
    return self.Chrom


def mutation_swap(self):
    for i in range(self.size_pop):
        if np.random.rand() < self.prob_mut:
            self.Chrom[i] = swap(self.Chrom[i])
    return self.Chrom


def change_maks(individual):
    a = np.random.choice(np.where(False == individual)[0][1:])
    c = np.random.randint(2)
    if c == 0 and a + 1 < individual.shape[0]:
        individual[a], individual[a + 1] = individual[a + 1], individual[a]
    else:
        individual[a], individual[a - 1] = individual[a - 1], individual[a]

    return individual


def mutation_vrptw1(self):
    for i in range(self.size_pop):
        if np.random.rand() < self.prob_mut:
            sel = np.random.randint(4)
            if sel == 0:
                self.tmpChrom[i] = swap(self.tmpChrom[i])
            elif sel == 1:
                self.tmpChrom[i] = swap(self.tmpChrom[i])
            elif sel == 2:
                self.tmpChrom[i] = swap(self.tmpChrom[i])
            else:
                self.tmpChrom_index[i] = change_maks(self.tmpChrom_index[i])

    self.Chrom[:] = 0
    self.Chrom[self.tmpChrom_index] = self.tmpChrom.reshape(-1) + 1
    return self.Chrom
